package product

import (
	"context"
	"fmt"
	"strconv"
	"strings"
	"time"

	"mall/common/globalkey"
	"mall/common/modelx"
	"mall/common/moneyx"
	"mall/service/backend/common/errorx"
	"mall/service/backend/common/i18n"
	"mall/service/product/model"
	"mall/service/product/rpc/internal/svc"
	"mall/service/product/rpc/internal/utils/errorhandler"
	"mall/service/product/rpc/types/product"

	"github.com/gosimple/slug"
	"github.com/volatiletech/null/v8"
	"github.com/zeromicro/go-zero/core/logx"
)

type CreateProductLogic struct {
	ctx    context.Context
	svcCtx *svc.ServiceContext
	logx.Logger
}

func NewCreateProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateProductLogic {
	return &CreateProductLogic{
		ctx:    ctx,
		svcCtx: svcCtx,
		Logger: logx.WithContext(ctx),
	}
}

func (l *CreateProductLogic) CreateProduct(in *product.ProductInfo) (*product.BaseIDResp, error) {
	if in.Title == nil || len(strings.TrimSpace(*in.Title)) == 0 {
		return nil, errorx.NewInvalidArgumentError("product.titleRequired")
	}

	if in.ProductCode == nil || len(strings.TrimSpace(*in.ProductCode)) == 0 {
		return nil, errorx.NewInvalidArgumentError("product.codeRequired")
	}

	reqTitle := strings.TrimSpace(*in.Title)
	filterMap := make(map[string]map[string][]any)
	filterMap[model.ProductProductColumns.Title] = map[string][]any{
		"equalTo": {reqTitle},
	}
	if in.CategoryId != nil {
		filterMap[model.ProductProductColumns.CategoryID] = map[string][]any{
			"equalTo": {*in.CategoryId},
		}
	}
	var reqSlug string
	if in.Slug != nil && len(strings.TrimSpace(*in.Slug)) > 0 {
		reqSlug = strings.TrimSpace(*in.Slug)
		filterMap[model.ProductProductColumns.Slug] = map[string][]any{
			"equalTo": {reqSlug},
		}
	}
	_, count, err := l.svcCtx.CurrentProduct.GetList(l.ctx, model.M{
		"pageSize": uint64(1),
		"select":   ",",
	}, 0, filterMap)
	if err != nil {
		return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
	}

	if count > 0 {
		return nil, errorx.NewInvalidArgumentError("product.titleAlreadyExists")
	}

	tx, err := l.svcCtx.DB.BeginTx(l.ctx, nil)
	if err != nil {
		return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
	}

	now := time.Now()
	goods := &model.ProductProduct{
		Title:               reqTitle,
		ProductCode:         strings.TrimSpace(*in.ProductCode),
		ProductDate:         now,
		ProductModifiedDate: now,
	}

	var text string
	if len(reqSlug) > 0 {
		goods.Slug = reqSlug
		slug.Lowercase = false
		text = slug.Make(reqSlug)
	}
	if in.Excerpt != nil && len(strings.TrimSpace(*in.Excerpt)) > 0 {
		goods.Excerpt = strings.TrimSpace(*in.Excerpt)
	}

	var visibility uint32
	if in.Visibility != nil && *in.Visibility > 0 {
		visibility = *in.Visibility
	} else {
		visibility = globalkey.PostVisibilityPublic
	}

	var publicDate time.Time
	var isFuturePublicDate bool
	if in.PublicDate != nil && len(*in.PublicDate) > 0 {
		if localTime, err := modelx.ParseLocalTime(*in.PublicDate); err == nil {
			publicDate = localTime
			isFuturePublicDate = true
		}
	}

	var status uint16
	var pendingStatus int32 // 审核后的状态
	if in.Status != nil {
		if *in.Status == uint32(globalkey.PostStatusPublish) { // 发布
			if isFuturePublicDate { // 未来发布
				status = uint16(globalkey.PostStatusFuture) // 暂为future状态
			} else { // 立即发布
				if visibility == globalkey.PostVisibilityPrivate {
					status = uint16(globalkey.PostStatusPrivate)
				} else {
					status = uint16(globalkey.PostStatusPublish)
				}
			}
		} else { // 其他状态
			status = uint16(*in.Status)
			if isFuturePublicDate { // 未来发布
				pendingStatus = globalkey.PostStatusFuture // 审核后为future状态
			} else { // 立即发布
				if visibility == globalkey.PostVisibilityPrivate {
					pendingStatus = globalkey.PostStatusPrivate
				} else {
					pendingStatus = globalkey.PostStatusPublish
				}
			}
		}
		goods.Status = status
	} else {
		goods.Status = uint16(globalkey.PostStatusAutoDraft)
	}

	if in.Sort != nil {
		goods.SortOrder = int(*in.Sort)
	} else {
		goods.SortOrder = int(9999)
	}
	if in.CategoryId != nil {
		goods.CategoryID = *in.CategoryId
	}
	if in.ParentId != nil {
		goods.ParentID = *in.ParentId
	}
	if in.CreatedBy != nil {
		goods.ProductAuthorID = *in.CreatedBy
	}

	if isFuturePublicDate {
		goods.ProductDate = publicDate
	}
	if len(text) > 0 {
		goods.GUID = "/product/" + text
	}
	var sticky string
	if in.Sticky != nil && *in.Sticky > 0 {
		switch int32(*in.Sticky) {
		case globalkey.BlogStickyTypeCategory:
			sticky = "category"
		case globalkey.BlogStickyTypeTag:
			sticky = "tag"
		default:
			sticky = "frontpage"
		}
	}

	rel := &model.ProductRel{
		Detail: &model.ProductDetail{
			Content: *in.Content,
		},
		CategoryIds: in.CategoryIds,
		TagIds:      in.TagIds,
	}
	if in.ContentFiltered != nil {
		rel.Detail.ContentFiltered = *in.ContentFiltered
	}
	if in.Tracing != nil {
		rel.Detail.Tracing = null.StringFrom(*in.Tracing)
	}
	if in.TracingFiltered != nil {
		rel.Detail.TracingFiltered = null.StringFrom(*in.TracingFiltered)
	}

	if len(in.Meta) > 0 {
		for _, v := range in.Meta {
			if v.Key == nil || len(strings.TrimSpace(*v.Key)) == 0 {
				continue
			}

			if v.Value == nil || len(strings.TrimSpace(*v.Value)) == 0 {
				continue
			}

			rel.Meta = append(rel.Meta, &model.ProductMetum{
				MetaKey:   strings.TrimSpace(*v.Key),
				MetaValue: null.StringFrom(strings.TrimSpace(*v.Value)),
			})
		}
	}
	if len(sticky) > 0 {
		rel.Meta = append(rel.Meta, &model.ProductMetum{
			MetaKey:   "_sticky",
			MetaValue: null.StringFrom(sticky),
		})
	}
	if pendingStatus > 0 {
		rel.Meta = append(rel.Meta, &model.ProductMetum{
			MetaKey:   "_pending_status",
			MetaValue: null.StringFrom(strconv.FormatInt(int64(pendingStatus), 10)),
		})
	}
	if in.SellPoint != nil && len(strings.TrimSpace(*in.SellPoint)) > 0 {
		rel.Meta = append(rel.Meta, &model.ProductMetum{
			MetaKey:   "_sell_point",
			MetaValue: null.StringFrom(strings.TrimSpace(*in.SellPoint)),
		})
	}
	if in.StockUnit != nil && len(strings.TrimSpace(*in.StockUnit)) > 0 {
		rel.Meta = append(rel.Meta, &model.ProductMetum{
			MetaKey:   "_stock_unit",
			MetaValue: null.StringFrom(strings.TrimSpace(*in.StockUnit)),
		})
	}
	if in.ShowStock != nil && *in.ShowStock > 0 {
		rel.Meta = append(rel.Meta, &model.ProductMetum{
			MetaKey:   "_show_stock",
			MetaValue: null.StringFrom("1"),
		})
	}

	allMediaMap := make(map[string]string)
	if len(in.AllMedia) > 0 {
		for _, v := range in.AllMedia {
			if v.Id != nil && v.Url != nil && len(*v.Id) > 0 && len(*v.Url) > 0 {
				allMediaMap[*v.Id] = *v.Url
			}
		}
	}

	if len(in.NewMedia) > 0 {
		var thumbnailOrder uint
		var thumbnailId uint64
		var thumbnailUrl string
		for idx, v := range in.NewMedia {
			var url string
			if v.Url != nil && len(*v.Url) > 0 {
				url = *v.Url
			}
			var uri string
			if v.Uri != nil && len(*v.Uri) > 0 {
				uri = *v.Uri
				if len(url) == 0 {
					url = uri
				}
			}
			if len(url) == 0 {
				continue
			}

			var thumbnail uint8
			if v.Thumbnail != nil && *v.Thumbnail > 0 {
				thumbnail = uint8(*v.Thumbnail)
			}
			var order uint
			if v.Order != nil {
				order = uint(*v.Order)
			} else {
				order = uint(idx) + globalkey.AttachmentOrderOffset
			}
			var altText string
			if v.AltText != nil && len(*v.AltText) > 0 {
				altText = *v.AltText
			}

			var meta []string
			if v.Name != nil && len(*v.Name) > 0 {
				meta = append(meta, "filename:"+*v.Name)
				if len(altText) == 0 {
					altText = *v.Name
				}
			}
			if v.MetaData != nil && len(*v.MetaData) > 0 {
				meta = append(meta, *v.MetaData)
			}
			if v.MimeType != nil && len(*v.MimeType) > 0 {
				meta = append(meta, "mimeType:"+*v.MimeType)
			}
			metaData := strings.Join(meta, ";")

			var mediaId uint64
			if v.Id != nil && len(*v.Id) > 0 {
				if i, err := strconv.ParseInt(*v.Id, 10, 64); err == nil {
					mediaId = uint64(i)
				}
			}

			media := &model.ProductAttachmentRelationship{
				AttachmentID:        mediaId,
				AttachmentThumbnail: thumbnail,
				AttachmentOrder:     order,
				AttachmentImageURL:  url,
				AttachmentImageAlt:  altText,
				AttachmentImageMeta: metaData,
			}

			if thumbnail == 0 && mediaId > 0 {
				if uri, ok := allMediaMap[*v.Id]; ok {
					media.AttachmentSource = uri
				}
			}

			rel.Media = append(rel.Media, media)

			if thumbnail > 0 && (len(thumbnailUrl) == 0 || thumbnailOrder > order) {
				thumbnailOrder = order
				thumbnailId = mediaId
				thumbnailUrl = url
			}
		}

		if len(rel.Meta) > 0 {
			rel.Meta = append(rel.Meta, &model.ProductMetum{
				MetaKey:   "_photo_count",
				MetaValue: null.StringFrom(strconv.FormatInt(int64(len(rel.Media)), 10)),
			})
		}

		if len(thumbnailUrl) > 0 {
			rel.Meta = append(rel.Meta, &model.ProductMetum{
				MetaKey:   "_thumbnail_id",
				MetaValue: null.StringFrom(strconv.FormatUint(thumbnailId, 10)),
			})
			rel.Meta = append(rel.Meta, &model.ProductMetum{
				MetaKey:   "_thumbnail_url",
				MetaValue: null.StringFrom(thumbnailUrl),
			})
		}
	}

	//RelationIds: brands:44657_-_佩枪朱丽叶_-_Juliette Has A Gun;perfumes:40035_-_帕尔玛之水 蓝色地中海-阿玛菲无花果, 2006_-_Acqua di Parma Blu Mediterraneo - Fico di Amalfi, 2006
	if in.RelationIds != nil && len(strings.TrimSpace(*in.RelationIds)) > 0 {
		relation := l.ParseRelationIds(strings.TrimSpace(*in.RelationIds))
		if relation.ToEntryID > 0 || relation.ToEntryGroupID > 0 {
			rel.RelationShips = []*model.ProductKnowledgeBaseRelationship{relation}
		}
	}

	if len(in.Attrs) > 0 {
		for _, attr := range in.Attrs {
			var hasImage uint8
			if attr.HasMedia != nil && *attr.HasMedia > 0 {
				hasImage = 1
			}
			var options []*model.ProductAttributeOption
			if len(attr.Options) > 0 {
				for _, opt := range attr.Options {
					option := &model.ProductAttributeOption{
						Status: int8(globalkey.StatusEnabled),
					}
					if opt.Id != nil {
						option.TaxonomyOptionID = *opt.Id // attribute_option id
					}
					if opt.Label != nil && len(strings.TrimSpace(*opt.Label)) > 0 {
						option.Name = strings.TrimSpace(*opt.Label)
					}
					if opt.Slug != nil && len(strings.TrimSpace(*opt.Slug)) > 0 {
						option.Slug = strings.TrimSpace(*opt.Slug)
					}
					if opt.MediaId != nil {
						if i, err := strconv.ParseInt(*opt.MediaId, 10, 64); err == nil {
							option.ThumbnailID = uint64(i)
						}
					}
					if opt.MediaUrl != nil && len(strings.TrimSpace(*opt.MediaUrl)) > 0 {
						option.ThumbnailURL = strings.TrimSpace(*opt.MediaUrl)
					}
					options = append(options, option)
				}
			}
			attribute := &model.ProductAttribute{
				HasImage: hasImage,
			}
			if attr.TermId != nil {
				attribute.TermID = *attr.TermId
			}
			if attr.Id != nil {
				attribute.Taxonomy = *attr.Id // attribute id
			}
			if attr.Label != nil && len(strings.TrimSpace(*attr.Label)) > 0 {
				attribute.Name = strings.TrimSpace(*attr.Label)
			}
			if attr.Slug != nil && len(strings.TrimSpace(*attr.Slug)) > 0 {
				attribute.Slug = strings.TrimSpace(*attr.Slug)
			}
			rel.Attributes = append(rel.Attributes, &model.AttributeAndOptions{
				Attribute: attribute,
				Options:   options,
			})
		}
	}

	rel.OptionKeyMap = make(map[int8]string)
	if len(in.Specs) > 0 {
		var tmpIdx int8 = -1
		for _, spec := range in.Specs {
			var hasImage uint8
			if spec.HasMedia != nil && *spec.HasMedia > 0 {
				hasImage = 1
			}
			var options []*model.ProductAttributeOption
			if len(spec.Options) > 0 {
				for _, opt := range spec.Options {
					tmpIdx -= 1
					option := &model.ProductAttributeOption{
						Status: tmpIdx,
					}
					if opt.Key != nil {
						rel.OptionKeyMap[tmpIdx] = *opt.Key
					}
					if opt.Id != nil {
						option.TaxonomyOptionID = *opt.Id // attribute_option id
					}
					if opt.Label != nil && len(strings.TrimSpace(*opt.Label)) > 0 {
						option.Name = strings.TrimSpace(*opt.Label)
					}
					if opt.Slug != nil && len(strings.TrimSpace(*opt.Slug)) > 0 {
						option.Slug = strings.TrimSpace(*opt.Slug)
					}
					if opt.MediaId != nil {
						if i, err := strconv.ParseInt(*opt.MediaId, 10, 64); err == nil {
							option.ThumbnailID = uint64(i)
						}
					}
					if opt.MediaUrl != nil && len(strings.TrimSpace(*opt.MediaUrl)) > 0 {
						option.ThumbnailURL = strings.TrimSpace(*opt.MediaUrl)
					}
					options = append(options, option)
				}
			}
			attribute := &model.ProductAttribute{
				HasImage:             hasImage,
				IsVariationAttribute: 1,
			}
			if spec.TermId != nil {
				attribute.TermID = *spec.TermId
			}
			if spec.Id != nil {
				attribute.Taxonomy = *spec.Id // attribute id
			}
			if spec.Label != nil && len(strings.TrimSpace(*spec.Label)) > 0 {
				attribute.Name = strings.TrimSpace(*spec.Label)
			}
			if spec.Slug != nil && len(strings.TrimSpace(*spec.Slug)) > 0 {
				attribute.Slug = strings.TrimSpace(*spec.Slug)
			}
			rel.Attributes = append(rel.Attributes, &model.AttributeAndOptions{
				Attribute: attribute,
				Options:   options,
			})
		}
	}

	if len(in.Skus) > 0 {
		var saleCount uint32
		var gemPriceMin int64
		var gemPriceMax int64
		var retailPriceMin int64
		var retailPriceMax int64
		for _, sku := range in.Skus {
			variation := &model.ProductVariation{}
			if sku.GemPrice != nil && len(*sku.GemPrice) > 0 {
				gemPrice, err := moneyx.ParseGem(*sku.GemPrice)
				if err != nil {
					logx.Errorw("failed to ParseGem", logx.Field("detail", err.Error()))
				} else {
					variation.GemPrice = uint64(gemPrice)
					if gemPriceMin == 0 || gemPriceMin > gemPrice {
						gemPriceMin = gemPrice
					}
					if gemPriceMax == 0 || gemPriceMax < gemPrice {
						gemPriceMax = gemPrice
					}
				}
			}
			if sku.RetailPrice != nil && len(*sku.RetailPrice) > 0 {
				retailPrice, err := moneyx.ParseAmount(*sku.RetailPrice)
				if err != nil {
					logx.Errorw("failed to ParseAmount", logx.Field("detail", err.Error()))
				} else {
					variation.RetailPrice = uint64(retailPrice)
					if retailPriceMin == 0 || retailPriceMin > retailPrice {
						retailPriceMin = retailPrice
					}
					if retailPriceMax == 0 || retailPriceMax < retailPrice {
						retailPriceMax = retailPrice
					}
				}
			}
			if sku.CounterPrice != nil && len(*sku.CounterPrice) > 0 {
				counterPrice, err := moneyx.ParseAmount(*sku.CounterPrice)
				if err != nil {
					logx.Errorw("failed to ParseAmount", logx.Field("detail", err.Error()))
				} else {
					variation.CounterPrice = uint64(counterPrice)
				}
			}
			if sku.Weight != nil && len(*sku.Weight) > 0 {
				weight, err := moneyx.ParseWeight(*sku.Weight)
				if err != nil {
					logx.Errorw("failed to ParseWeight", logx.Field("detail", err.Error()))
				} else {
					variation.Weight = uint(weight)
				}
			}
			if sku.SkuCode != nil && len(*sku.SkuCode) > 0 {
				variation.SkuCode = *sku.SkuCode
			}
			if sku.Status != nil {
				variation.Status = uint8(*sku.Status)
			}
			var variationMeta []*model.ProductVariationMetum
			if sku.SaleCount != nil && *sku.SaleCount > 0 {
				variationMeta = append(variationMeta, &model.ProductVariationMetum{
					MetaKey:   "_pre_sale_count",
					MetaValue: null.StringFrom(strconv.FormatInt(int64(*sku.SaleCount), 10)),
				})
				saleCount += *sku.SaleCount
			}
			if sku.MediaId != nil && len(*sku.MediaId) > 0 {
				variationMeta = append(variationMeta, &model.ProductVariationMetum{
					MetaKey:   "_thumbnail_id",
					MetaValue: null.StringFrom(*sku.MediaId),
				})
			}
			if sku.MediaUrl != nil && len(strings.TrimSpace(*sku.MediaUrl)) > 0 {
				variationMeta = append(variationMeta, &model.ProductVariationMetum{
					MetaKey:   "_thumbnail_url",
					MetaValue: null.StringFrom(*sku.MediaUrl),
				})
			}
			if len(sku.Specs) > 0 {
				skuStr := l.FormatSpecs(sku.Specs)
				if len(skuStr) > 0 && sku.Key != nil && len(strings.TrimSpace(*sku.Key)) > 0 {
					variationMeta = append(variationMeta, &model.ProductVariationMetum{
						MetaKey:   "_sku",
						MetaValue: null.StringFrom(skuStr),
					})
					variationMeta = append(variationMeta, &model.ProductVariationMetum{
						MetaKey:   "_sku_key",
						MetaValue: null.StringFrom(strings.TrimSpace(*sku.Key)),
					})
				}
			}
			rel.Variations = append(rel.Variations, &model.VariationAndMeta{
				Variation: variation,
				Meta:      variationMeta,
			})
		}
		if saleCount > 0 {
			rel.Meta = append(rel.Meta, &model.ProductMetum{
				MetaKey:   "_pre_sale_count",
				MetaValue: null.StringFrom(strconv.FormatInt(int64(saleCount), 10)),
			})
		}
		if gemPriceMin > 0 {
			rel.Meta = append(rel.Meta, &model.ProductMetum{
				MetaKey:   "_gem_price_min",
				MetaValue: null.StringFrom(moneyx.FormatGem(gemPriceMin)),
			})
		}
		if gemPriceMax > 0 {
			rel.Meta = append(rel.Meta, &model.ProductMetum{
				MetaKey:   "_gem_price_max",
				MetaValue: null.StringFrom(moneyx.FormatGem(gemPriceMax)),
			})
		}
		if retailPriceMin > 0 {
			rel.Meta = append(rel.Meta, &model.ProductMetum{
				MetaKey:   "_retail_price_min",
				MetaValue: null.StringFrom(moneyx.FormatAmount(retailPriceMin)),
			})
		}
		if retailPriceMax > 0 {
			rel.Meta = append(rel.Meta, &model.ProductMetum{
				MetaKey:   "_retail_price_max",
				MetaValue: null.StringFrom(moneyx.FormatAmount(retailPriceMax)),
			})
		}
	}

	err = l.svcCtx.CurrentProduct.Insert(l.ctx, tx, goods, rel)
	if err != nil {
		tx.Rollback()
		return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
	}

	tx.Commit()

	return &product.BaseIDResp{
		Id:  goods.ID,
		Msg: i18n.CreateSuccess,
	}, nil
}

func (l *CreateProductLogic) ParseRelationIds(relationIds string) *model.ProductKnowledgeBaseRelationship {
	relationship := &model.ProductKnowledgeBaseRelationship{}
	pairs := strings.Split(relationIds, arrayKeyValueSeparator)
	if len(pairs) > 0 {
		for _, pair := range pairs {
			parts := strings.Split(pair, keyValueSeparator)
			if len(parts) == 2 && len(parts[0]) > 0 && len(parts[1]) > 0 {
				if parts[0] == "perfumes" {
					values := strings.Split(parts[1], complexLSVSeparator)
					if len(values) == 3 && len(values[0]) > 0 {
						if i, err := strconv.ParseInt(values[0], 10, 64); err == nil {
							relationship.ToEntryID = uint64(i)
							relationship.ToEntryName = values[1]
							relationship.ToEntrySlug = values[2]
						}
					}
				} else if parts[0] == "brands" {
					values := strings.SplitN(parts[1], complexLSVSeparator, 3)
					if len(values) == 3 && len(values[0]) > 0 {
						if i, err := strconv.ParseInt(values[0], 10, 64); err == nil {
							relationship.ToEntryGroupID = uint64(i)
							relationship.ToEntryGroupName = values[1]
							relationship.ToEntryGroupSlug = values[2]
						}
					}
				}
			}
		}
	}

	return relationship
}

func (l *CreateProductLogic) FormatSpecs(specs []*product.ProductAttrInfo) (res string) {
	if len(specs) > 0 {
		var items []string
		for _, spec := range specs {
			var opts []string
			if len(spec.Options) > 0 {
				for _, opt := range spec.Options {
					if opt.Id != nil && opt.Label != nil {
						var slug string
						if opt.Slug != nil {
							slug = *opt.Slug
						}
						opts = append(opts, fmt.Sprintf("%d%s%s%s%s", *opt.Id, LSVSeparator, *opt.Label, LSVSeparator, slug))
					}
				}
			}
			if spec.Id != nil && spec.Label != nil {
				var slug string
				if spec.Slug != nil {
					slug = *spec.Slug
				}
				items = append(
					items,
					fmt.Sprintf("%d%s%s%s%s%s%s", *spec.Id, LSVSeparator, *spec.Label, LSVSeparator, slug, keyValueSeparator, strings.Join(opts, arrayValueSeparator)),
				)
			}
		}
		return strings.Join(items, arrayKeyValueSeparator)
	}

	return
}
